#include "RevelEncoder.h"
#include "revelluabindings.h"
#include "../core/Logger.h"
#include "../core/ObjectFactory.h"
#include "revel.h"

RegisterCppObj<RevelEncoder> g_register_revelencoder_object;

// this is needed to get static objects to initialize
int returnFourPlusX(int x)
{
	return x + 4;
}

struct RevelEncoder::RevelData
{
	Revel_Params revParams;
	Revel_VideoFrame frame;	
};

RevelEncoder::RevelEncoder()
{
	_nogood = false;
	_currentlyEncoding = false;
	_encHandle = -1;

	_revData = new RevelData;
	_revData->frame.pixels = NULL;
	
    if (REVEL_API_VERSION != Revel_GetApiVersion())
    {
        LOG(logERROR) << "ERROR: Revel version mismatch!";
        LOG(logERROR) << "Headers: version " << REVEL_VERSION << ", API version " << REVEL_API_VERSION;
        LOG(logERROR) << "Library: version " <<  Revel_GetVersion() << ", API version " << Revel_GetApiVersion();
		_nogood = true;
    }
}

RevelEncoder::~RevelEncoder()
{
	finalizeEncoding();
	delete(_revData);
}

void RevelEncoder::startEncoding(int imgWidth, int imgHeight, float quality, float framerate, const std::string& filename)
{
	LOG(logDEBUG) << "Starting encoding... (quality: " << quality << ")";
	if(!_currentlyEncoding && !_nogood)
	{
		Revel_Error revError;
		if(_encHandle == -1)
		{
			revError = Revel_CreateEncoder(&_encHandle);
			if(revError != REVEL_ERR_NONE)
			{
				LOG(logERROR) << "Serious revel error when trying to start encoding: " << revError;
				_nogood = true;
				return;
			}
		}
		Revel_InitializeParams(&(_revData->revParams));
		_revData->revParams.width = imgWidth;
		_revData->revParams.height = imgHeight;
		_revData->revParams.frameRate = framerate;
		_revData->revParams.quality = quality;
		_revData->revParams.codec = REVEL_CD_XVID;
		_revData->revParams.hasAudio = 0;
		_w = imgWidth;
		_h = imgHeight;

		revError = Revel_EncodeStart(_encHandle, filename.c_str(), &(_revData->revParams));
		if(revError != REVEL_ERR_NONE)
		{
			LOG(logERROR) << "Revel encoder start error (maybe bad filename?): " << revError;
		}

		_currentlyEncoding = true;
		_revData->frame.width = _w;
		_revData->frame.height = _h;
		_revData->frame.pixelFormat = REVEL_PF_RGBA;
		_revData->frame.bytesPerPixel = 4;
		_revData->frame.pixels = new UCHAR[_w * _h * 4];
	}
	else
	{
		if(_currentlyEncoding)
			LOG(logERROR) << "Cannot start revel encoding; already encoding.";
		else // _nogood
			LOG(logERROR) << "Cannot start revel encoding: a previous error is preventing all further operations.";
	}
}

void RevelEncoder::finalizeEncoding()
{
	LOG(logDEBUG) << "Finalizing encoding...";
	if(_currentlyEncoding)
	{
		Revel_Error revErr = Revel_EncodeEnd(_encHandle, NULL);
		if(revErr != REVEL_ERR_NONE)
			LOG(logERROR) << "Revel error when finalizing encoding: " << revErr;

		delete[] (UCHAR*)(_revData->frame.pixels);
		_currentlyEncoding = false;
	}
	if(_encHandle != -1)
	{
		Revel_DestroyEncoder(_encHandle);
		_encHandle = -1;

	}
}

void RevelEncoder::encodeFrame(SimpleImage* frame)
{
	//LOG(logDEBUG2) << "Encoding frame...";
	if(!_currentlyEncoding || _nogood)
	{
		if(_nogood)
			LOG(logERROR) << "Revel: Some previous bad error stopping frame encoding.";
		else
			LOG(logERROR) << "Revel: Cannot encode frame: encoding not started.";
		return;
	}

	// this is the potentially slow way of doing this, but whatever
	for(int y = 0; y < _h; ++y)
	{
		for(int x = 0; x < _w; ++x)
		{
			int xp = (x + y * _w)*4;
			for(int c = 0; c < 3; ++c)
			{
				((UCHAR*)(_revData->frame.pixels))[xp + c] = frame->get(x, _h - y, c);
			}
			((UCHAR*)(_revData->frame.pixels))[xp + 3] = 0; // alpha
		}
	}

	Revel_Error revErr = Revel_EncodeFrame(_encHandle, &(_revData->frame), NULL);
	if(revErr != REVEL_ERR_NONE)
	{
		LOG(logERROR) << "Some serious revel error when trying to encode a frame: " << revErr;
		_nogood = true;
	}
}

// Genero AbstractObject stuff below

// Return the c++ type id
int RevelEncoder::getCppTypeId()
{
	return 3;
}

// Returns whether this can be cast to a given type
bool RevelEncoder::isType(int id)
{
	return false;
}

// Return the c++ type as a string 
std::string RevelEncoder::getCppTypeName()
{
	return "RevelEncoder";
}

// returns a description of this thing
std::string RevelEncoder::getDescription()
{
	return "RevelEncoder: writes xvid files";
}

// create another one of these
AbstractObject* RevelEncoder::newInstance()
{
	return new RevelEncoder;
}

// init & update
void RevelEncoder::init()
{
	// nothing to do
}

void RevelEncoder::update(double dt)
{
	// nothing to do
}

// push its static functions into the given lua table
void RevelEncoder::pushStaticFuncs(lua_State* L)
{
	RevelBindings::pushStaticFuncs(L);
}
